/*
 * This file is part of the Sx Framework Library.
 * 
 * Copyright (C) 2014 University of Colorado Denver
 * <min.choi@ucdenver.edu> <shane.transue@ucdenver.edu>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */
#include <sxBezierEditController.h>
#include <sxCanvas.h>

const static unsigned int DEFAULT_POINT_SELECTION_DISTANCE = 5;
const static bool DEFAULT_GRID_SNAP = true;
const static unsigned int DEFAULT_N = 20;
const static float DEFAULT_GRID_SNAP_SPACING = 1.0f / 60.0f;
const static bool DEFAULT_USE_VARIABLE_POINTS = false;

Sx::Interface::BezierEditController::BezierEditController(Viewport* viewport, const std::shared_ptr<Canvas>& canvas, const std::shared_ptr<ScenePickingController>& pickingController, const std::shared_ptr<RendererImplementation>& cameraRenderer, const std::shared_ptr<RendererImplementation>& plotRenderer, const std::shared_ptr<RendererImplementation>& bezierRenderer) :
	PlotController(viewport, canvas, pickingController, cameraRenderer, plotRenderer) {
	
	this->curve = std::make_shared<Bezier_Archetype>(bezierRenderer, Eigen::Vector3f(1.0f, 1.0f, 0.0f), Eigen::Vector3f(8.0f, 3.0f, 0.0f), Eigen::Vector3f(2.0f, 3.0f, 0.0f), Eigen::Vector3f(3.0f, 4.0f, 0.0f), DEFAULT_N);
	this->curvePointSelectionDistance = DEFAULT_POINT_SELECTION_DISTANCE;
	this->gridSnap = DEFAULT_GRID_SNAP;
	this->snapXSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->snapYSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->snapZSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->variablePointCurve = DEFAULT_USE_VARIABLE_POINTS;
}

Sx::Interface::BezierEditController::BezierEditController(Viewport* viewport, const std::shared_ptr<Canvas>& canvas, const std::shared_ptr<ScenePickingController>& pickingController, const std::shared_ptr<RendererImplementation>& cameraRenderer, const std::shared_ptr<RendererImplementation>& plotRenderer, const std::shared_ptr<RendererImplementation>& bezierRenderer, unsigned int nxTicks, unsigned int pxTicks, unsigned int nyTicks, unsigned int pyTicks, unsigned int nzTicks, unsigned int pzTicks, float spacing) :
	PlotController(viewport, canvas, pickingController, cameraRenderer, plotRenderer, nxTicks, pxTicks, nyTicks, pyTicks, nzTicks, pzTicks, spacing) {

	this->curve = std::make_shared<Bezier_Archetype>(bezierRenderer, Eigen::Vector3f(1.0f, 1.0f, 0.0f), Eigen::Vector3f(8.0f, 3.0f, 0.0f), Eigen::Vector3f(2.0f, 3.0f, 0.0f), Eigen::Vector3f(3.0f, 4.0f, 0.0f), DEFAULT_N);
	this->curvePointSelectionDistance = DEFAULT_POINT_SELECTION_DISTANCE;
	this->gridSnap = DEFAULT_GRID_SNAP;
	this->snapXSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->snapYSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->snapZSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->variablePointCurve = DEFAULT_USE_VARIABLE_POINTS;
}

Sx::Interface::BezierEditController::BezierEditController(Viewport* viewport, const std::shared_ptr<Canvas>& canvas, const std::shared_ptr<ScenePickingController>& pickingController, const std::shared_ptr<RendererImplementation>& cameraRenderer, const std::shared_ptr<RendererImplementation>& plotRenderer, const std::shared_ptr<RendererImplementation>& bezierRenderer, const Eigen::Vector3f& endPoint0, const Eigen::Vector3f& endPoint1, const Eigen::Vector3f& controlPoint0, const Eigen::Vector3f& controlPoint1) :
	PlotController(viewport, canvas, pickingController, cameraRenderer, plotRenderer) {
	
	this->curve = std::make_shared<Bezier_Archetype>(bezierRenderer, endPoint0, endPoint1, controlPoint0, controlPoint1, DEFAULT_N);
	this->curvePointSelectionDistance = DEFAULT_POINT_SELECTION_DISTANCE;
	this->gridSnap = DEFAULT_GRID_SNAP;
	this->snapXSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->snapYSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->snapZSpacing = DEFAULT_GRID_SNAP_SPACING;
	this->variablePointCurve = DEFAULT_USE_VARIABLE_POINTS;
}

Sx::Interface::BezierEditController::~BezierEditController() {}

bool Sx::Interface::BezierEditController::initialize() {
	PlotController::initialize();
	this->curve->construct();
	this->curve->isOneToOneFunction(Math::X);
	this->curve->hasLoop(Math::X);
	return true;
}

void Sx::Interface::BezierEditController::render() const {
	PlotController::render();
	if ( this->curve == nullptr ) return;
	this->curve->render();
}

/* 
 * This function tests the provided mouse coordinates to see if any points of
 * the curve have been intersected. Since this function is used for determining
 * when the user wants to move the selected points, it will only return true
 * if the point has been picked and it is selected.
 */
bool Sx::Interface::BezierEditController::intersectCurve(int x, int y) {
	if ( this->curve == nullptr ) return false;
	if ( this->curve->getObject() == nullptr ) return false;

	Eigen::Vector2i mouse(x, y);
	Eigen::Vector2i projected;
	std::shared_ptr<BezierCurve> curve = this->curve->getObject();

	this->project(curve->getEndPoint(false).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		if ( curve->getEndPoint(false).isSelected() ) return true;
	}

	this->project(curve->getEndPoint(true).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		if ( curve->getEndPoint(true).isSelected() ) return true;
	}

	this->project(curve->getControlPoint(false).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		if ( curve->getControlPoint(false).isSelected() ) return true;
	}

	this->project(curve->getControlPoint(true).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		if ( curve->getControlPoint(true).isSelected() ) return true;
	}

	return false;
}

/*
 * Picks the closest point to x, y. If it is within the selection distance then
 * it will select it and return (this is different than the intersect function
 * in that it will flag the point as selected).
 */
bool Sx::Interface::BezierEditController::pickCurve(int x, int y) {
	if ( this->curve == nullptr ) return false;
	if ( this->curve->getObject() == nullptr ) return false;

	Eigen::Vector2i mouse(x, y);
	Eigen::Vector2i projected;
	std::shared_ptr<BezierCurve> curve = this->curve->getObject();

	bool e0 = false, e1 = false, c0 = false, c1 = false;
	this->project(curve->getEndPoint(false).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		curve->setEndPointSelected(false, true);
		e0 = true;
	}

	this->project(curve->getEndPoint(true).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		curve->setEndPointSelected(true, true);
		e1 = true;
	}

	this->project(curve->getControlPoint(false).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		curve->setControlPointSelected(false, true);
		c0 = true;
	}

	this->project(curve->getControlPoint(true).getPosition(), projected);
	if ( Math::Distance(mouse, projected) < this->curvePointSelectionDistance ) {
		curve->setControlPointSelected(true, true);
		c1 = true;
	}

	if ( e0 || e1 || c0 || c1 ) return true;
	else return false;
}

void Sx::Interface::BezierEditController::mousePressEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	PlotController::mousePressEvent(x, y, button, modifierKey);
	if ( this->intersectCurve(x, y ) ) {
		this->draggingCurve = true;
		this->inEdit = true;
	}
}

void Sx::Interface::BezierEditController::mouseReleaseEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	PlotController::mouseReleaseEvent(x, y, button, modifierKey);
	if ( this->curve == nullptr ) return;

	if ( this->draggingCurve == true ) this->mouseSnapOnRelase();
	this->draggingCurve = false;
	this->inEdit = false;
}

void Sx::Interface::BezierEditController::mouseClickEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	PlotController::mouseClickEvent(x, y, button, modifierKey);
	if ( this->curve == nullptr ) return;

	if ( modifierKey == Interface::MODIFIER_CONTROL ) {
		this->pickCurve(x, y);
	}
	else {
		this->curve->deselect();
		this->pickCurve(x, y);
	}
}

void Sx::Interface::BezierEditController::mouseMoveEvent(int x, int y, MouseButton button, ModifierKey modifierKey) {
	if ( this->curve == nullptr ) return;

	if ( this->draggingCurve && this->curve->isSelected() == true && button == Interface::LEFT_MOUSE_BUTTON ) this->mouseEditBezierCurve(x, y, button, modifierKey);
	else PlotController::mouseMoveEvent(x, y, button, modifierKey);
}

void Sx::Interface::BezierEditController::mouseWheelEvent(int delta) {
	PlotController::mouseWheelEvent(delta);
}

/* TODO: Needs an implementation for perspective selection movement. */
bool Sx::Interface::BezierEditController::mouseEditBezierCurve(int x, int y, MouseButton button, ModifierKey modifierKey) {
	if ( this->camera == nullptr ) return false;

	this->inEdit = true;
	Eigen::Vector2i diff;
	Eigen::Vector3f displacement = Eigen::Vector3f::Zero();

	std::shared_ptr<Camera_Archetype::ObjectType> camera = this->camera->getObject();
	int lastPosDiffX = this->positionDifference.x();
	int lastPosDiffY = this->positionDifference.y();
	
	float moveX = this->positionDifference.x() * camera->getConX();
	float moveY = this->positionDifference.y() * camera->getConY();

	if ( this->camera->getObject()->getProjection() == Math::ORTHOGRAPHIC ) {
		Math::View view = camera->getView();
		switch ( view ) {
		case Math::FRONT:
			displacement.x() = -1.0f * moveX;
			displacement.y() = moveY;
		break;
		case Math::BACK:
			displacement.x() = moveX;
			displacement.y() = moveY;
		break;
		case Math::TOP: 
			displacement.x() = -1.0f * moveX;
			displacement.z() = -1.0f * moveY;
		break;
		case Math::BOTTOM:
			displacement.x() = -1.0f * moveX;
			displacement.z() = moveY;
		break;
		case Math::LEFT:
			displacement.z() = moveX;
			displacement.y() = moveY;
		break;
		case Math::RIGHT:
			displacement.z() = -1.0f * moveX;
			displacement.y() = moveY;
		break;
		};
	}
	else {
		std::cerr << "[BezierEditController:mouseEditBezierCurve] Error: Perspective camera Bezier curve editing not implemented." << std::endl;
		displacement = Eigen::Vector3f::Zero();
	}

	this->curve->moveSelected(displacement);

	//----------------------------------------------------------------------
	// Update curve properties.
	//----------------------------------------------------------------------
	this->curve->isOneToOneFunction(Math::X);
	this->curve->hasLoop(Math::X);

	return true;
}

bool Sx::Interface::BezierEditController::mouseSnapOnRelase() {
	if ( this->curve == nullptr ) return false;
	if ( this->curve->getObject() == nullptr ) return false;
	Eigen::Vector3f intersectedPoint, displacement, snapPosition;

	if ( curve->isEndPointSelected(false) ) intersectedPoint = curve->getObject()->getEndPoint(false).getPosition();
	if ( curve->isEndPointSelected(true) ) intersectedPoint = curve->getObject()->getEndPoint(true).getPosition();
	if ( curve->isControlPointSelected(false) ) intersectedPoint = curve->getObject()->getControlPoint(false).getPosition();
	if ( curve->isControlPointSelected(true) ) intersectedPoint = curve->getObject()->getControlPoint(true).getPosition();

	snapPosition = Math::Snap3D(this->snapXSpacing, this->snapYSpacing, this->snapZSpacing, intersectedPoint);
	displacement = (snapPosition - intersectedPoint);

	if ( curve->isEndPointSelected(false) ) this->curve->moveEndPoint(false, displacement);
	if ( curve->isEndPointSelected(true) ) this->curve->moveEndPoint(true, displacement);
	if ( curve->isControlPointSelected(false) ) this->curve->moveControlPoint(false, displacement);
	if ( curve->isControlPointSelected(true) ) this->curve->moveControlPoint(true, displacement);
	
	if ( this->variablePointCurve == true ) this->constructVariablePointCurve(Math::X);
	return true;
}

void Sx::Interface::BezierEditController::setVariablePointCurve(bool b) {
	this->variablePointCurve = b;
	this->constructVariablePointCurve(Math::X);
}

float Sx::Interface::BezierEditController::getCurveLength(Math::Axis axis) const {
	if ( this->curve == nullptr ) return 0.0f;
	std::shared_ptr<BezierCurve> curve = this->curve->getObject();
	if ( curve == nullptr ) return 0.0f;
	if ( axis == Math::X || axis == Math::NX )
		return std::abs(curve->getEndPoint(true).getPosition().x() - curve->getEndPoint(false).getPosition().x());
	if ( axis == Math::Y || axis == Math::NY )
		return std::abs(curve->getEndPoint(true).getPosition().y() - curve->getEndPoint(false).getPosition().y());
	if ( axis == Math::Z || axis == Math::NZ )
		return std::abs(curve->getEndPoint(true).getPosition().z() - curve->getEndPoint(false).getPosition().z());
	return 0.0f;
}

unsigned int Sx::Interface::BezierEditController::getCurvePointCount() const {
	if ( this->curve == nullptr ) return 0;
	return this->curve->getCurvePointCount();
}

bool Sx::Interface::BezierEditController::getCurveApproximation(Util::ArrayList<Eigen::Vector3f>& approximationPoints) {
	if ( this->curve == nullptr ) return false;
	return this->curve->getCurveApproximation(approximationPoints);
}

bool Sx::Interface::BezierEditController::constructVariablePointCurve(Math::Axis axis) {
	float p0 = 0.0f, p1 = 1.0f, len = 0.0f;
	std::shared_ptr<BezierCurve> curve = this->curve->getObject();

	if ( axis == Math::NX || axis == Math::X ) {
		p0 = curve->getEndPoint(false).getPosition().x();
		p1 = curve->getEndPoint(true).getPosition().x();
	}
	else if ( axis == Math::NY || axis == Math::Y ) {
		p0 = curve->getEndPoint(false).getPosition().y();
		p1 = curve->getEndPoint(true).getPosition().y();
	}
	else if ( axis == Math::NZ || axis == Math::Z ) {

	}
	else {
		std::cerr << "[BezierEditController:constructVariablePointCurve] Error: Invalid axis provided." << std::endl;
		return false;
	}

	if ( p0 < p1 ) len = std::abs(p1 - p0);
	else len = std::abs(p0 - p1);
	
	unsigned int pointCount = static_cast<unsigned int>(Math::Round(len / this->snapXSpacing) );
	this->curve->setCurvePointCount(pointCount);
	return this->curve->construct();
}

void Sx::Interface::BezierEditController::setSnap(bool enabled) {
	this->gridSnap = enabled;
}

void Sx::Interface::BezierEditController::setSnapSpacing(float xSpacing, float ySpacing, float zSpacing) {
	this->snapXSpacing = xSpacing;
	this->snapYSpacing = ySpacing;
	this->snapZSpacing = zSpacing;
}

void Sx::Interface::BezierEditController::setSnapSpacing(const Eigen::Vector3f& spacing) {
	this->snapXSpacing = spacing.x();
	this->snapYSpacing = spacing.y();
	this->snapZSpacing = spacing.z();
}

bool Sx::Interface::BezierEditController::hasGridSnap() const {
	return this->gridSnap;
}

float Sx::Interface::BezierEditController::getXSnapSpacing() const {
	return this->snapXSpacing;
}

float Sx::Interface::BezierEditController::getYSnapSpacing() const {
	return this->snapYSpacing;
}

float Sx::Interface::BezierEditController::getZSnapSpacing() const {
	return this->snapZSpacing;
}

Eigen::Vector3f Sx::Interface::BezierEditController::getSnapSpacing() const {
	return Eigen::Vector3f(this->snapXSpacing, this->snapYSpacing, this->snapZSpacing);
}

long Sx::Interface::BezierEditController::getEndPointSnapPositionX(bool index) const {
	if ( this->gridSnap == false ) {
		std::cerr << "[BezierEditController:getEndPointSnapPositionX] Error: Grid snap no enabled." << std::endl;
		return 0u;
	}

	const std::shared_ptr<BezierCurve>& curve = this->curve->getObject();
	float curPos = curve->getEndPoint(index).getPosition().x();
	return static_cast<long>(Math::Round(curPos / this->snapXSpacing));
}

long Sx::Interface::BezierEditController::getEndPointSnapPositionY(bool index) const {
	if ( this->gridSnap == false ) {
		std::cerr << "[BezierEditController:getEndPointSnapPositionY] Error: Grid snap no enabled." << std::endl;
		return 0u;
	}

	const std::shared_ptr<BezierCurve>& curve = this->curve->getObject();
	float curPos = curve->getEndPoint(index).getPosition().y();
	return static_cast<long>(Math::Round(curPos / this->snapYSpacing));
}

long Sx::Interface::BezierEditController::getEndPointSnapPositionZ(bool index) const {
	if ( this->gridSnap == false ) {
		std::cerr << "[BezierEditController:getEndPointSnapPositionZ] Error: Grid snap no enabled." << std::endl;
		return 0u;
	}

	const std::shared_ptr<BezierCurve>& curve = this->curve->getObject();
	float curPos = curve->getEndPoint(index).getPosition().z();
	return static_cast<long>(Math::Round(curPos / this->snapZSpacing));
}
